package dev.langchain4j.model.output;

import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.Nullable;

import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

import static dev.langchain4j.internal.Utils.copy;
import static dev.langchain4j.internal.ValidationUtils.ensureNotNull;

/**
 * Represents the response from various types of models, including language, chat, embedding, and moderation models.
 * This class encapsulates the generated content, token usage statistics, finish reason, and response metadata.
 *
 * @param <T> The type of content generated by the model.
 */
public class Response<T> {

    @NonNull
    private final T content;
    private final TokenUsage tokenUsage;
    private final FinishReason finishReason;
    @NonNull
    private final Map<String, Object> metadata;

    /**
     * Create a new Response.
     *
     * <p>Will contain {@code null} {@code TokenUsage} and {@code FinishReason}</p>
     *
     * @param content the content to wrap.
     */
    public Response(@NonNull T content) {
        this(content, null, null, Map.of());
    }

    /**
     * Create a new Response.
     *
     * @param content      the content to wrap.
     * @param tokenUsage   the token usage statistics, or {@code null}.
     * @param finishReason the finish reason, or {@code null}.
     */
    public Response(@NonNull T content, TokenUsage tokenUsage, FinishReason finishReason) {
        this(content, tokenUsage, finishReason, Map.of());
    }

    /**
     * Create a new Response.
     *
     * @param content      the content to wrap.
     * @param tokenUsage   the token usage statistics, or {@code null}.
     * @param finishReason the finish reason, or {@code null}.
     * @param metadata     the response metadata, or {@code null}.
     */
    public Response(@NonNull T content,
                    TokenUsage tokenUsage,
                    FinishReason finishReason,
                    @Nullable Map<String, Object> metadata) {
        this.content = ensureNotNull(content, "content");
        this.tokenUsage = tokenUsage;
        this.finishReason = finishReason;
        this.metadata = copy(metadata);
    }

    /**
     * Get the content.
     *
     * @return the content.
     */
    @NonNull
    public T content() {
        return content;
    }

    /**
     * Get the token usage statistics.
     *
     * @return the token usage statistics, or {@code null}.
     */
    public TokenUsage tokenUsage() {
        return tokenUsage;
    }

    /**
     * Get the finish reason.
     *
     * @return the finish reason, or {@code null}.
     */
    public FinishReason finishReason() {
        return finishReason;
    }

    /**
     * Get the response metadata.
     *
     * @return the response metadata.
     */
    @NonNull
    public Map<String, Object> metadata() {
        return metadata;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Response<?> that = (Response<?>) o;
        return Objects.equals(this.content, that.content)
                && Objects.equals(this.tokenUsage, that.tokenUsage)
                && Objects.equals(this.finishReason, that.finishReason)
                && Objects.equals(this.metadata, that.metadata);
    }

    @Override
    public int hashCode() {
        return Objects.hash(content, tokenUsage, finishReason, metadata);
    }

    @Override
    public String toString() {
        return "Response {" +
                " content = " + content +
                ", tokenUsage = " + tokenUsage +
                ", finishReason = " + finishReason +
                ", metadata = " + metadata +
                " }";
    }

    /**
     * Create a new Response.
     *
     * @param content the content to wrap.
     * @param <T>     the type of content.
     * @return the new Response.
     */
    @NonNull
    public static <T> Response<T> from(@NonNull T content) {
        return new Response<>(content);
    }

    /**
     * Create a new Response.
     *
     * @param content    the content to wrap.
     * @param tokenUsage the token usage statistics.
     * @param <T>        the type of content.
     * @return the new Response.
     */
    @NonNull
    public static <T> Response<T> from(@NonNull T content, TokenUsage tokenUsage) {
        return new Response<>(content, tokenUsage, null);
    }

    /**
     * Create a new Response.
     *
     * @param content      the content to wrap.
     * @param tokenUsage   the token usage statistics.
     * @param finishReason the finish reason.
     * @param <T>          the type of content.
     * @return the new Response.
     */
    @NonNull
    public static <T> Response<T> from(@NonNull T content, TokenUsage tokenUsage, FinishReason finishReason) {
        return new Response<>(content, tokenUsage, finishReason);
    }

    /**
     * Create a new Response.
     *
     * @param content      the content to wrap.
     * @param tokenUsage   the token usage statistics.
     * @param finishReason the finish reason.
     * @param metadata     the response metadata.
     * @param <T>          the type of content.
     * @return the new Response.
     */
    @NonNull
    public static <T> Response<T> from(@NonNull T content,
                                       TokenUsage tokenUsage,
                                       FinishReason finishReason,
                                       @Nullable Map<String, Object> metadata) {
        return new Response<>(content, tokenUsage, finishReason, metadata);
    }
}
